Grafana Faro 初始化設定只需要幾行程式碼即可完成,同時也提供了更細節的設置屬性,這些屬性都是可以依據每個使用場景或需求來調用。而本章節中想要介紹更進階的 initializeFaro config 屬性,包括貫穿整個可觀測性的 instrumentations 和自定義的 instrumentation,以及自定義的 log 用法和在資料傳送前的多一層過濾,最後再加上系統中可以運用的所有 metas 屬性。
const faro = initializeFaro({
// 其他設置...
instrumentations: [
...getWebInstrumentations(),
new TracingInstrumentation(),
],
});
instrumentations
屬性包含了 Faro 中所有的 Instrumentation 功能。在第二十三天的文章中,已介紹過 Faro 預設提供 7 種測量方式,專門針對 Web 的測量詳細資料被封裝在 getWebInstrumentations
函數中,這些方式包括 Console、Error、Web Vitals、Session tracking、View tracking 和 Performance Instrumentation。由於預設會使用全部的測量方式,因此可以直接調用 getWebInstrumentations
。不過,根據不同使用情境,也可以從 faro
中分別引入後依需求將特定的測量方法設置在 instrumentations
屬性中。
此外,針對網路請求,從 web-tracking
package 引入的 TracingInstrumentation
也包含了多項可設定的屬性,如 fetchInstrumentationOptions
和 xhrInstrumentationOptions
,可為網路請求做更細緻的定義。
當然,Faro 也支援自定義的 Instrumentations 功能,讓開發者能夠傳送官方尚未提供的資訊。若開發的功能具有實用性,也可以考慮提交 PR,為社群做出貢獻。
主要功能是根據開發者需求,自定義 Web SDK 的行為,並會回傳一組監控 Web 應用工具,預設是包含 ErrorsInstrumentation、WebVitalsInstrumentation、SessionInstrumentation 和 ViewInstrumentation 的陣列,函數本身接受一個參數為options
物件,也可以透過參數設定添加 PerformanceInstrumentation 和 ConsoleInstrumentation。options
包含三個主要屬性:
enablePerformanceInstrumentation:
預設值為 true
,會在預設的陣列中使用 unshift
確保它在其他儀器化工具之前初始化。當設置為 false
時,PerformanceInstrumentation
則不會被添加到回傳的陣列中。
captureConsole:
預設值為 true
,會以 push 的方法被添加到陣列的最後一順位,若設置為 false
時,則不會被添加到回傳的陣列中。
captureConsoleDisabledLevels:
僅在 captureConsole
為 true
時有效。可以指定哪些 console 級別應該被禁用,會作為 disabledLevels
傳遞給 ConsoleInstrumentation
,並防止該工具捕捉這些級別的 log。包含的 level 有 TRACE、DEBUG、INFO、LOG、WARN 及 ERROR。
instrumentations: [
...getWebInstrumentations({
captureConsole: true,
captureConsoleDisabledLevels: [LogLevel.WARN],
})
],
TracingInstrumentation 是用於設置和管理 OpenTelemetry 追蹤功能。它繼承自 BaseInstrumentation,並提供了一種方式來集成 OpenTelemetry 的追蹤功能到 Faro 中。但由於 OpenTelemetry 的追蹤功能體積較為龐大,所以特別獨立出一個 web-tracing 的 package。預設中已經使用了 FetchInstrumentation 和 XMLHttpRequestInstrumentation 作為請求的測量。
TracingInstrumentation 的初始化設置提供了一些選項讓開發者可以自定義追蹤行為,也都是繼承 OpenTelemetry 的追蹤屬性,屬性如下:
resourceAttributes:設置額外的資源屬性到所有的 trace。下圖是預設的屬性,可以依據格式做延伸。
propagator:預設使用 W3CTraceContextPropagator,主要作用是將追蹤資訊(例如 trace ID、span ID)或其他上下文資訊(如 Baggage 的鍵值對)注入到請求中,然後在不同服務處理請求時提取這些數據,以保持跨服務的追蹤和上下文一致性。常見的 propagator 包括:
contextManager:預設使用 ZoneContextManager,責管理程式執行上下文。應用程式可以在不同部分之間傳遞和共享上下文數據(如 trace ID、span ID 等),確保上下文在不同的執行流程或非同步操作中保持一致性。對於 node.js 和瀏覽器中常見的有:
async_hooks
API 來追蹤非同步操作。AsyncLocalStorage
API,簡化及提高效能。contextManager
,依賴 Zone.js 元件。instrumentations:指定自定義的 instrumentation 列表。可以從 OpenTelemetry 的 package 中引入 DocumentLoadInstrumentation、UserInteractionInstrumentation。
spanProcessor:負責管理 span
的生命周期。主要作用是在 span
結束時決定如何處理這些追蹤數據,例如傳送到追蹤後端系統、過濾或修改 span
。預設是使用 BatchSpanProcessor,有以下幾種類型:
span
結束時立即導出,適合開發環境。span
,適合正式環境,能提高效能。new TracingInstrumentation({
resourceAttributes: { 'custom.attribute': 'value' },
propagator: new W3CTraceContextPropagator(),
contextManager: new AsyncLocalStorageContextManager(),
instrumentations: [
new UserInteractionInstrumentation({
eventNames: ['click', 'dblclick', 'submit', 'keypress'],
}),
],
spanProcessor: new SimpleSpanProcessor(),
})
讓開發者可以自定義 OpenTelemetry 的 instrumentation 行為,包括設置 fetchInstrumentationOptions 和 xhrInstrumentationOptions 讓開發者對 Fetch 和 XMLHttpRequest 的追蹤行為進行更細緻的控制。例如指定特定的 URL 模式來傳播追蹤 Header,設置自定義屬性到 span 上,或者忽略某些網路事件。
new TracingInstrumentation({
instrumentationOptions: {
fetchInstrumentationOptions: {
ignoreUrls: [/example\.com/],
applyCustomAttributesOnSpan: (span, request, result) => {
span.setAttribute('custom.fetch.attribute', 'value');
}
},
xhrInstrumentationOptions: {
ignoreNetworkEvents: true,
applyCustomAttributesOnSpan: (span, xhr) => {
span.setAttribute('custom.xhr.attribute', 'value');
}
}
}
})
其他還有一些自訂定義的屬性,例如可以對錯誤訊息的解析 parseStacktrace、處理複雜的物件和過濾敏感資訊的 logArgsSerializer 或是對事件傳遞的前處理 beforeSend,這些函數雖然在一般使用狀況下比較少用到,但如果有大流量或針對傳遞的事件需要更精細的對收集資料作分類收集,我想這些功能是非常實用的,接下來讓我們一一介紹及範例:
一個可以提取特定的錯誤訊息或格式化 stack trace 的函數,資料型態是接收一個 error 的物件參數,並回傳一個 Stacktrace 的物件。 error 物件包含 columnNumber、stacktrace 以及基本的 Error 屬性,因此可以藉由這些屬性進行判斷過濾來建立自定義的錯誤 log 格式。只要回傳的格式為 Stacktrace ,是一個以 frame 為 key,value 為 ExceptionStackFrame[]
的物件,而 ExceptionStackFrame 又包含了檔案名稱、function、行號、列號等資訊:
const customParseStacktrace = (error) => {
const frames = error.stack?.split('\n').map(line => {
// 解析每一行,提取相關資料
// 返回 ExceptionStackFrame 物件
}) || [];
return frames;
};
const config = {
// ...其他設置
parseStacktrace: customParseStacktrace,
};
💡 Stacktrace(堆疊追蹤)
是一個在程式發生錯誤或異常時,記錄程式執行過程的工具。它會顯示程式執行時函數呼叫的順序,從最初的函數呼叫到發生錯誤的地方,讓開發者可以追蹤和了解程式是如何到達錯誤發生點的。
但前端應用程式在 production 模式時,通常會將所有的 JavaScript 檔案經過 minify(壓縮)和 bundle(打包)成一個或多個檔案,以減少檔案大小和載入時間。在這種情況下,當發生錯誤時,Stacktrace 中所顯示的行數和位置會對應到 bundle 檔案中的行數,而不是原始檔案中的具體行數。例如:
Error: test is not defined
at callUndefined (http://localhost:5173/assets/index-f490af9f.js:30289:5)
這樣的問題需要加上 Source Map 的功能來解決,而 Faro 也開發了 Webpack 和 Rollup/Vite 版本的 build plugin 工具,提供使用者更精準的錯誤資訊。但要注意的是:結至 2024 年 9 月,Source Map 上傳功能在 Grafana Cloud 中目前僅支援 AWS 和 GCP 平台,Azure 尚未支援。
// **Webpack**
import FaroSourcemapUploaderPlugin from '@grafana/faro-webpack-plugin';
// **Rollup/Vite**
import faroUploader from '@grafana/faro-rollup-plugin';
這是一個可以用來處理複雜的物件、過濾敏感資訊,或者以特定格式輸出 log 參數的函數。函數接收一個未知類型的陣列(通常是傳遞給 console.log 等方法的參數),並返回一個序列化後的陣列。通常可以針對參數的類型一一做特殊的處理,只要最後回傳的值是字串即可,這個功能在收集複雜的 log 格式時是非常實用的。以下為範例:
const customLogArgsSerializer = (args) => {
return args.map(arg => {
if (typeof arg === 'object' && arg !== null) {
// 對物件進行特殊處理
return JSON.stringify(arg);
}
// 對其他類型的參數進行處理
return String(arg);
});
};
const config = {
logArgsSerializer: customLogArgsSerializer,
};
讓開發者在數據被發送到伺服器之前對其進行檢查、修改或過濾。這個功能提供了一個強大的 hook,可以在數據離開客戶端之前進行最後的處理。這個函數接收一個 TransportItem 作為參數,並回傳一個修改後的 TransportItem 或 null。如果是 null,則該項目將不會被發送。可以用於特定類型的錯誤或 log 被過濾、添加或修改額外的數據資料、發送資料前移除或遮蔽敏感資訊,或是基於某些條件決定是否發送收集的數據,使用方法如下:
beforeSend: (item: TransportItem) => {
if (item.type === TransportItemType.EXCEPTION && (item.payload as ErrorEvent).type === 'TypeError') {
return null;
}
return item;
},
一群資料,其內容提供了有關於另一群資料的資訊。
以上是關於 metadata 的定義,看起來很繞舌,但 Metas 在 Faro 中其實是包含所有客戶端資料的一組物件,用於補充和描述應用程式的狀態、環境和使用者資訊,且必須以 key-value 的形式存在。Metas 可以是靜態或動態:
在 Faro 中 Metas 被分為八種類型,包括 App、SDK、User、Session、Page、Browser、View 和 K6,除了 User 和 K6 其他皆會在預設中進行收集,以下是各類型所收集的資料:
將訊號與特定應用程式綁定,並且在 session 期間不應變更。包含應用程式的名稱、版本、命名空間等屬性。name、version、namespace、release 和 environment 。
使用 ua-parser-js
的套件解析應用程式執行的瀏覽器環境。屬性包含瀏覽器名稱、版本、操作系統等資訊。name、version(瀏覽器版本)、os(操作系統名稱和版本)、mobile(是否在行動裝置上運行)、userAgent(使用者代理資訊)、language(使用者偏好語言)、brands(瀏覽器品牌及版本)、viewportWidth 和 viewportHeight。
識別訊號來自的頁面。該屬性可自動變更或由使用者自訂。屬性包括 url: 當前頁面的 URL、id: 頁面 ID、attributes: 額外的頁面屬性。要特別注意,如果是使用 React 的專案,由於 SPA 的特性,所以切換路由時不會有新的紀錄,需要重新整理才會更新資料,如果需要紀錄可使用下方的 view 屬性。url 是由 location.href 取得,而 id 則是 location.pathname。
包含 Faro SDK 本身的資訊,如名稱和版本。這些數據是由 SDK 自動處理的,使用者不需要額外設定。屬性有 name(核心庫名稱)、version(SDK 版本)、integrations(使用中的整合工具及其版本)。
連接訊號的 session 資訊。屬性有 id 是預設長度為 10 的 session ID 和 attributes,同常會儲存 previousSession 的 ID。而如果在 config 中沒有另外設定自定義的 session id 產生方法(generateSessionId),預設使用的函數為:
export function genShortID(length = 10): string {
return Array.from(Array(length))
.map(() => alphabet[Math.floor(Math.random() * alphabet.length)]!)
.join('');
}
將訊號與特定使用者綁定,包含使用者 ID、名稱、電子郵件等屬性。這些資訊可以在初始化時或使用 faro.api 動態設置。由於這個屬性會需要使用者的個人資訊,所以會依據網頁是否有會員制度,以及會員登入與否進行設定,預設也不會帶入任何資料。
💡NOTICE
Faro 有提到,SDK 本身不會主動收集使用者的資料,所以各種資料收集規範都依開發的需求調整,可以自行選擇是否設置。
用於標註訊號發生的 View,可以用來追蹤動態變化的 UI 元素,而不需依賴路由變化。通常是在 SPA 的應用程式會使用,由於無法依據 location.href 或 pathname 偵測 url 路徑的變化,所以需要在路由切換時以 faro.api.setView
手動加上。
Grafana K6 是一個專為效能測試設計的開源工具,用來執行負載和壓力測試。底層是 Go 語言所設計,但可以透過 JavaScript 編寫測試腳本,模擬大量使用者同時訪問應用程式或 API,幫助開發者檢測系統在高流量下的響應速度、穩定性和效能瓶頸。
因此如果 Faro 在 K6 測試環境中運行,會自動附加 K6 meta,使用者不需要作新增或修改,其中包含 testRunId
來標註當前的 K6 測試。
筆者語錄
Grafana Faro 不僅能透過簡單的設置來實現豐富的監控功能,還提供了多層的自定義設置選項,讓開發者能夠針對不同的需求調整數據收集與過濾方式。隨著更進階的功能如 OpenTelemetry 的加入,Faro 將可觀測性帶入了一個全新的層次,讓我們在追蹤與分析應用程式時擁有更大的彈性與準確性。
https://github.com/grafana/faro-web-sdk/issues/618
https://grafana.com/docs/k6/latest/
https://zh.wikipedia.org/zh-tw/元数据
https://opentelemetry.io/docs/languages/js/resources/
https://opentelemetry.io/docs/specs/otel/context/api-propagators/
https://opentelemetry.io/docs/demo/services/frontend/